Skip to content

fix: multi-account sync disappearing calendar and stuck import - #2772

Merged
cursor[bot] merged 7 commits into
mainfrom
cursor/multi-account-sync-fixes-3714
Aug 13, 2026
Merged

fix: multi-account sync disappearing calendar and stuck import#2772
cursor[bot] merged 7 commits into
mainfrom
cursor/multi-account-sync-fixes-3714

Conversation

@tyler-dane

@tyler-dane tyler-dane commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Email/password signup followed by Settings → Add account hid the original Compass calendar and left "Adding your calendar…" stuck. This PR fixes both the UI attribution and the import-state push/pull path, plus a sign-in fork guard.

Summary

  1. Local calendar vanished. CalendarList hid provider === "local" once any Google connection existed (LCV3). That premise is false for email/pw signup → add Google. Day view also dropped local columns while Week kept them. Empty account groups were filtered out, so a just-connected account had no section to hang "Adding your calendar…" on.

  2. Import never converged in the UI. importing → healthy was computed lazily only on sync GET /connections. Bootstrap completion did not push a connection invalidation. SSE handlers refreshed metadata without force, focus reconciliation was disabled during IMPORTING, and a missing events resource stayed importing forever. Reconnect clobbered lastHealthyAt, so an established account looked like a first import.

  3. Add account could bind to reconnect. useConnectGoogle pinned connectionId whenever any account was RECONNECT_REQUIRED, so Settings "Add account" became a reconnect and failed with "Reconnect account does not match the named connection".

  4. Sign-in fork. An authenticated user hitting the Google sign-in path (?auth=POST /api/signinup) with an unrecognized Google account silently created a new Compass user.

Sync — deterministic import-state convergence

  • Call refreshConnectionState after bootstrapCatchup and subscriptionMaintain unsupported set bootstrapState to ready (try/catch; never fail the job).
  • Missing events resource uses the calendar row's updatedAt as the stall basis → delayed after BOOTSTRAP_STALLED_AFTER_MS.
  • Refresh CTA enqueues coalesced calendarListSync per connection so discovery heals resource-less calendars.
  • lastSyncedAt / lastHealthyAt move to $setOnInsert so reconnect does not clobber them.

Web — metadata refresh discipline

  • SSE handlers, stream open/reopen, and focus always refreshUserMetadata({ force: true }).
  • Provider refresh stays gated to HEALTHY/ATTENTION; metadata is not.
  • 20s poll while any single connection is connecting / importing / catchingUp.
  • Concurrent force calls still chain onto one trailing fetch; epoch counter drops stale writes.

Web — sidebar + Day view

  • Local calendar is its own Compass-email section (no collapse/status/CTA) when Google accounts exist. LCV1/LCV2 still exclude local as a create target (getWritableCalendars / getDefaultTargetCalendar).
  • Empty seeded account groups are kept so importing accounts render a header.
  • Day columns are active+visible calendars, matching Week. Click-to-create on a Day column uses the same writable set, so the restored local column is visible and toggleable but not a create target after Google is connected.

Web — Add account

  • useConnectGoogle({ newAccount: true }) always begins with {}. Settings Accounts section uses it.

Auth fork guard

  • ?auth= is ignored (and stripped) while a session exists.
  • handleGoogleAuth refuses SIGNUP when input.session is present: "You're already signed in — use Settings → Add account to connect this Google account."

Architecture invariants: docs/architecture/multi-account-sync.md.

Simplicity

Kept zustand for user metadata (refresh discipline, not a store migration). refreshConnectionState remains the only derived-state writer. No second SSE push channel. The Day create gate reuses getWritableCalendars instead of a new local-exclusion rule.

Automated validation

  • Package tests cover the reported bugs (sidebar local section + toggle, empty importing account header, Day local column visibility, metadata force/poll, reconnect timestamps, bootstrap invalidation, Add-account newAccount, ?auth= while signed in, SIGNUP-under-session).
  • Full Google OAuth add-account path was not exercised in this environment (no SuperTokens/Google secrets). Login flows were not run against an incomplete backend, per AGENTS.md.

Independent review

Confirmed finding fixed: Day click-to-create on the restored local column after Google connect (LCV1/LCV2). Residual: dragging a Google event onto the local Day column can still retarget it; duplicate sidebar headings if Compass email equals a connected Google email; SIGNUP guard runs after SuperTokens signInUpPOST.

Test plan

  • bun test:sync — 900 pass
  • bun test:web — 2195 pass, then DayCalendarGrid 31 pass after the create-target fix
  • bun test:backend — 307 pass, 1 skip
  • bun type-check
  • bun lint (pre-existing warnings only)
  • GitHub Test + CodeQL on the previous head were green; this head re-runs them
Open in Web Open in Cursor 

cursoragent and others added 6 commits August 13, 2026 00:08
Email/password signup then Add account hid the Compass calendar, left
"Adding your calendar…" stuck, and could silently fork a new user from
the Google sign-in path. Push derived connection state from bootstrap
completion, heal missing-resource and reconnect clobber, force-refresh
metadata on SSE/focus/poll, attribute local calendars per account, and
refuse SIGNUP under an existing session.

Co-authored-by: Tyler Dane <tyler-dane@users.noreply.github.com>
URL-param tests timed out because a process-wide useSession mock from
another file reported authenticated, so ?auth= never opened the modal.

Co-authored-by: Tyler Dane <tyler-dane@users.noreply.github.com>
Co-authored-by: Tyler Dane <tyler-dane@users.noreply.github.com>
Spy the shared BaseApi and window timers so process-wide module mocks
cannot hide the coalescing and import-poll behavior, and assert the
new hasExistingSession argument on handleGoogleAuth.

Co-authored-by: Tyler Dane <tyler-dane@users.noreply.github.com>
mock.module leaked into later files and hid BaseApi.get. Use a restored
spy and the same global timers as useMinuteTick so import polling stops
when the connection settles.

Co-authored-by: Tyler Dane <tyler-dane@users.noreply.github.com>
mock.module turned refreshUserMetadata into a no-op for later files, and
an unrestored BaseApi.get spy hid CalendarList's adapter error path.

Co-authored-by: Tyler Dane <tyler-dane@users.noreply.github.com>
@tyler-dane
tyler-dane marked this pull request as ready for review August 13, 2026 00:23
Restoring the local Day column made click-to-create land on Compass.
Gate that surface with getWritableCalendars so LCV1/LCV2 still hold.

Co-authored-by: Tyler Dane <tyler-dane@users.noreply.github.com>
@cursor
cursor Bot merged commit 3109926 into main Aug 13, 2026
20 checks passed
@cursor
cursor Bot deleted the cursor/multi-account-sync-fixes-3714 branch August 13, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants